package com.anji.plus.gaea.inf.module.dynamic.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.anji.plus.gaea.inf.constant.InfConstant;
import com.anji.plus.gaea.inf.constant.ResEnum;
import com.anji.plus.gaea.inf.event.DynamicSqlEventSource;
import com.anji.plus.gaea.inf.event.DynamicSqlExecuteSuccessEvent;
import com.anji.plus.gaea.inf.event.InfHistoryApplicationEvent;
import com.anji.plus.gaea.inf.event.InfHistoryParam;
import com.anji.plus.gaea.inf.exception.InfException;
import com.anji.plus.gaea.inf.extension.GaeaInfHepler;
import com.anji.plus.gaea.inf.module.dynamic.controller.dto.InfDynamicSqlDTO;
import com.anji.plus.gaea.inf.module.dynamic.controller.dto.SqlModel;
import com.anji.plus.gaea.inf.module.dynamic.controller.dto.SqlTestDemo;
import com.anji.plus.gaea.inf.module.dynamic.dao.DynamicMapper;
import com.anji.plus.gaea.inf.module.dynamic.entity.CheckInfDTO;
import com.anji.plus.gaea.inf.module.dynamic.service.DynamicService;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author ultrajiaming
 * @since 2021/3/18 16:28
 */
@Service
public class DynamicServiceImpl implements DynamicService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private DynamicMapper dynamicMapper;

    @Autowired
    private GaeaInfHepler gaeaInfHepler;

    @Autowired
    private ApplicationContext applicationContext;

    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,
            Runtime.getRuntime().availableProcessors(),
            0,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(),
            r -> new Thread(r, "dynamicService-sql-history-exec"),
            new ThreadPoolExecutor.CallerRunsPolicy());

    @Override
    public List<JSONObject> testDemo(SqlTestDemo sqlTestDemo) {
        String sqlResults = null;
        if (StringUtils.isNotBlank(sqlTestDemo.getCustomResults())) {
            sqlResults = sqlTestDemo.getCustomResults();
        } else if (StringUtils.isNotBlank(sqlTestDemo.getResponseParams())) {
                // 构建一个SqlModel
                List<JSONObject> jsonArray = JSON.parseArray(sqlTestDemo.getResponseParams(), JSONObject.class);
                if (!CollectionUtils.isEmpty(jsonArray)) {
                    sqlResults = jsonArray.stream().filter(js -> js.get(InfConstant.RESPONSE_PARAM_KEY_NAME) != null).map(js -> {
                        String name = js.get(InfConstant.RESPONSE_PARAM_KEY_NAME).toString();
                        Object mapObj = js.get(InfConstant.PARAM_KEY_MAP);
                        String mapping;
                        StringBuilder sb = new StringBuilder(" ");
                        sb.append(name).append(" as ");
                        if (mapObj == null) {
                            mapping = com.baomidou.mybatisplus.core.toolkit.StringUtils.underlineToCamel(name);
                        } else {
                            mapping = String.valueOf(mapObj);
                        }
                        sb.append(mapping);
                        return sb.toString();
                    }).collect(Collectors.joining(","));
                }
        }
        SqlModel sqlModel = new SqlModel(sqlTestDemo.getSqlSentence(), sqlTestDemo.getDynamicCondition(), sqlResults);

        InfDynamicSqlDTO infDynamicSql = new InfDynamicSqlDTO();
        BeanUtils.copyProperties(sqlTestDemo, infDynamicSql);

        CheckInfDTO checkInf = new CheckInfDTO();
        checkInf.setInfName(sqlTestDemo.getInfName());
        checkInf.setSqlModel(sqlModel);
        List<JSONObject> jsonObjects = this.doExecuteQuery(checkInf, infDynamicSql, false);
        return jsonObjects;
    }

    @Override
    public List<JSONObject> runTest(InfDynamicSqlDTO dynamicSqlDto) {
        CheckInfDTO checkInf = new CheckInfDTO();
        checkInf.setInfName(dynamicSqlDto.getInfName());
        gaeaInfHepler.checkInfTest(checkInf);
        List<JSONObject> jsonObjects = this.doExecuteQuery(checkInf, dynamicSqlDto, true);
        // 测试通过，修改接口状态
        String supplier = CollectionUtils.isEmpty(jsonObjects) ? null : JSON.toJSONString(Arrays.asList(jsonObjects.get(0)));
        gaeaInfHepler.postProcessRunTest(dynamicSqlDto.getInfName(), () -> supplier);
        return jsonObjects;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JSONObject> execute(InfDynamicSqlDTO dynamicSqlDto) {
        CheckInfDTO checkInf = new CheckInfDTO();
        checkInf.setAppId(dynamicSqlDto.getAppId());
        checkInf.setInfName(dynamicSqlDto.getInfName());
        gaeaInfHepler.checkInfExecute(checkInf);
        List<JSONObject> jsonObjects = this.doExecuteQuery(checkInf, dynamicSqlDto, true);
        publishExecuteDynamicSqlSuccessEvent(checkInf);
        return jsonObjects;
    }

    private void publishExecuteDynamicSqlSuccessEvent(CheckInfDTO checkInf) {
        DynamicSqlEventSource dynamicSqlEventSource = new DynamicSqlEventSource();
        BeanUtils.copyProperties(checkInf, dynamicSqlEventSource);
//        applicationContext.publishEvent(new DynamicSqlExecuteSuccessEvent(dynamicSqlEventSource));
        executor.execute(() -> applicationContext.publishEvent(new DynamicSqlExecuteSuccessEvent(dynamicSqlEventSource)));
    }


    public Map<String, Object> prepareParam(String queryParams) {
        Map<String, Object> param = new HashMap<>();
        if (null != queryParams && !"".equals(queryParams)) {
            try {
                param = JSONObject.parseObject(queryParams, Map.class);
            } catch (JSONException e) {
                throw InfException.code(ResEnum.JSON_FORMAT_ERROR.getCode());
            }
        }
        return param;
    }

    public List<JSONObject> doExecuteQuery(CheckInfDTO inf, InfDynamicSqlDTO dynamicSqlDto, boolean history) {
//        String queryParams = dynamicSqlDto.getQueryParams();
        JSONObject queryParams = dynamicSqlDto.getQueryParams();
//        Map<String, Object> param = this.prepareParam(queryParams);

        Map<String, Object> param = new HashMap<>();
        if (queryParams != null) {
            for (Map.Entry<String, Object> entry : queryParams.entrySet()) {
                param.put(entry.getKey(), entry.getValue());
            }
        }

        Map<String, Object> finalParam = new HashMap<>();
        SqlModel sqlModel = inf.getSqlModel();
        if (StringUtils.isNotBlank(dynamicSqlDto.getCustomResults())) {
            sqlModel.setSqlResults(dynamicSqlDto.getCustomResults());
        }
        String dynamicSql = sqlModel.getSqlSegment(param);
        finalParam.put(InfConstant.DYNAMIC_SQL_KEY, dynamicSql);
        if (param != null) {
            finalParam.putAll(param);
        }
        logger.info("执行SQL语句入参：{}", finalParam);
        boolean executeResult = true;
        String failureReason = null;
        try {
            return dynamicMapper.list(finalParam);
        } catch (BadSqlGrammarException e) {
            e.printStackTrace();
            executeResult = false;
            failureReason = e.getMessage();
            throw InfException.code(ResEnum.EXECUTE_SQL_ERROR.getCode()).args(new Object[]{JSON.toJSONString(finalParam), e.getMessage()});
        } finally {
            if (history) {
                // 另起线程，异步执行
                final boolean finalExecuteResult = executeResult;
                final String finalFailureReason = failureReason;
                executor.execute(() -> {
                    InfHistoryParam ih = new InfHistoryParam();
                    ih.setInfName(inf.getInfName());
                    ih.setInfDescription(inf.getInfDescription());
                    ih.setSqlSentence(dynamicSql);
                    ih.setParams(JSON.toJSONString(param));
                    ih.setExecuteResult(finalExecuteResult ? "0" : "1");
                    ih.setFailureReason(finalFailureReason != null && finalFailureReason.length() > 50000 ? finalFailureReason.substring(0, 50000) : finalFailureReason);
                    applicationContext.publishEvent(new InfHistoryApplicationEvent(ih));
//                saveInfHistory(inf, param, finalExecuteResult, finalFailureReason);
                });
            }
        }
    }


}
